﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LaughingDog.GameObjects;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace LaughingDog.Components
{
    /// <summary>
    /// Component used for drawing a single 2D Texture.
    /// </summary>
    public class Renderer : Component, IDrawableComponent
    {
        private Texture2D mTexture = null;       
        private uint[] mTextureData;
        private Color[] mColourData;
        private Color[,] mColourData2D;        
        private Vector2 mOrigin = Vector2.Zero;
        private Color mColour = Color.White;
        private SpriteEffects mEffects = SpriteEffects.None;
        private Single mLayerDepth = 0.5f;
        private Boolean mVisible = true;
        private Boolean mInCameraView = true;
        private Rectangle? mDestinationRectangle = null;
        private Rectangle? mSourceRectangle = null;
        private Matrix mMatrix;

        /// <summary>
        /// The texture to be drawn by this Renderer.
        /// </summary>
        public Texture2D Texture { get { return mTexture; } set { SetTexture(value); } }

        /// <summary>
        /// Gets the texture data associated with this Renderer component.
        /// </summary>
        public uint[] TextureData { get { return mTextureData; } }

        /// <summary>
        /// Gets the colour data associated with this Renderer component.
        /// </summary>
        public Color[] ColourData { get { return mColourData; } }

        /// <summary>
        /// Gets the colour data associated with this Renderer component in a 2D array.
        /// </summary>
        public Color[,] ColourData2D { get { return mColourData2D; } }

        /// <summary>
        /// Gets or sets the origin point of this Renderer. Default is the centre of the Texture.
        /// </summary>
        public Vector2 Origin { get { return mOrigin; } set { mOrigin = value; } }

        /// <summary>
        /// Gets or sets the colour that should be used when drawing this Renderer. Default is White.
        /// </summary>
        public Color Colour { get { return mColour; } set { mColour = value; } }

        /// <summary>
        /// Gets or sets the effect to be used when drawing this Renderer.
        /// </summary>
        public SpriteEffects Effects { get { return mEffects; } set { mEffects = value; } }

        /// <summary>
        /// The layer depth of this Renderer from 1 (highest) to 0 (lowest).
        /// </summary>
        public Single LayerDepth { get { return mLayerDepth; } set { mLayerDepth = value; } }

        /// <summary>
        /// Gets or sets a value whether this Renderer should be drawn.
        /// </summary>
        public Boolean Visible { get { return mVisible; } set { mVisible = value; } }

        /// <summary>
        /// Gets or sets a value whether this Renderer is currently in view of the active Camera. Should not be adjusted manually.
        /// </summary>
        public Boolean InCameraView { get { return mInCameraView; } set { mInCameraView = value; } }

        /// <summary>
        /// Gets or sets the destination the Renderer should be drawn. Should not be adjusted manually instead use the attached Transform to move the GameObject this Renderer is attached to.
        /// </summary>
        public Rectangle? DestinationRectangle { get { return mDestinationRectangle; } set { mDestinationRectangle = value; } }       

        /// <summary>
        /// Gets or sets the area of the Texture to draw. Default is Null so that it draws the whole Texture.
        /// </summary>
        public Rectangle? SourceRectangle { get { return mSourceRectangle; } set { mSourceRectangle = value; } }

        /// <summary>
        /// Gets the transformation matrix of this component.
        /// </summary>
        public Matrix Matrix { get { return mMatrix; } }

        /// <summary>
        /// Creates a new instance of a Renderer component.
        /// </summary>
        /// <param name="gameObject">The GameObject this Component should be attached to.</param>
        public Renderer(GameObject gameObject)
            : base(gameObject)
        {            
        }

        /// <summary>
        /// Updates this Renderer component.
        /// </summary>
        public override void Update()
        {
            mDestinationRectangle = new Rectangle((Int32)GameObject.Transform.Position.X, (Int32)GameObject.Transform.Position.Y, (Int32)GameObject.Transform.ScaledSize.X, (Int32)GameObject.Transform.ScaledSize.Y);

            UpdateTransform();

            base.Update();
        }

        private void UpdateTransform()
        {
            if (Renderer != null)
            {
                mMatrix = Matrix.CreateTranslation(new Vector3(-Renderer.Origin, 0.0f)) *
                            Matrix.CreateScale(Transform.Scale.X, Transform.Scale.Y, 0) *
                            Matrix.CreateRotationZ(Transform.RotationRadians) *
                            Matrix.CreateTranslation(new Vector3(Transform.Position, 0.0f));
            }
        }

        /// <summary>
        /// Draws this component.
        /// </summary>
        /// <param name="spriteBatch">The SpriteBatch to be used for drawing.</param>
        public void Draw(SpriteBatch spriteBatch)
        {
            if (mTexture == null)
                return;

            if (Visible && InCameraView)
            {
                if(mDestinationRectangle == null)
                    spriteBatch.Draw(mTexture, GameObject.Transform.Position, mSourceRectangle, mColour, GameObject.Transform.RotationRadians, mOrigin, GameObject.Transform.Scale, mEffects, mLayerDepth);                
                else
                    spriteBatch.Draw(mTexture, (Rectangle)mDestinationRectangle, mSourceRectangle, mColour, GameObject.Transform.RotationRadians, mOrigin, mEffects, mLayerDepth);
            }
        }        

        private void SetTexture(Texture2D texture)
        {
            mTexture = texture;
            GameObject.Transform.Size = new Vector2(mTexture.Width, mTexture.Height);
            mOrigin = GameObject.Transform.Size / 2;

            mTextureData = new uint[mTexture.Width * mTexture.Height];
            mTexture.GetData(mTextureData);

            mColourData = new Color[mTexture.Width * mTexture.Height];
            mTexture.GetData<Color>(mColourData);

            mColourData2D = new Color[texture.Width, texture.Height];
            for (int x = 0; x < texture.Width; x++)
                for (int y = 0; y < texture.Height; y++)
                    mColourData2D[x, y] = mColourData[x + y * texture.Width];            
        }
       
        public static Boolean IntersectPixelsTranslated(GameObject gameObjectA, GameObject gameObjectB)
        {
            Matrix matrixA =
                Matrix.CreateTranslation(new Vector3(-gameObjectA.Renderer.Origin, 0.0f)) *
                Matrix.CreateRotationZ(gameObjectA.Transform.RotationRadians) *
                Matrix.CreateTranslation(new Vector3(gameObjectA.Transform.Position, 0.0f));

            Matrix matrixB =
                 Matrix.CreateTranslation(new Vector3(-gameObjectB.Renderer.Origin, 0.0f)) *
                 Matrix.CreateRotationZ(gameObjectB.Transform.RotationRadians) *
                 Matrix.CreateTranslation(new Vector3(gameObjectB.Transform.Position, 0.0f));

            // Calculate a matrix which transforms from A's local space into
            // world space and then into B's local space
            Matrix transformAToB = matrixA * Matrix.Invert(matrixB);

            // For each row of pixels in A
            for (int yA = 0; yA < gameObjectA.Transform.Size.Y; yA++)
            {
                // For each pixel in this row
                for (int xA = 0; xA < gameObjectA.Transform.Size.X; xA++)
                {
                    // Calculate this pixel's location in B
                    Vector2 positionInB =
                        Vector2.Transform(new Vector2(xA, yA), transformAToB);

                    // Round to the nearest pixel
                    int xB = (int)Math.Round(positionInB.X);
                    int yB = (int)Math.Round(positionInB.Y);

                    // If the pixel lies within the bounds of B
                    if (0 <= xB && xB < gameObjectB.Transform.Size.X &&
                        0 <= yB && yB < gameObjectB.Transform.Size.Y)
                    {
                        // Get the colors of the overlapping pixels
                        Color colorA = gameObjectA.Renderer.ColourData[xA + yA * (Int32)gameObjectA.Transform.Size.X];
                        Color colorB = gameObjectB.Renderer.ColourData[xB + yB * (Int32)gameObjectB.Transform.Size.X];

                        // If both pixels are not completely transparent,
                        if (colorA.A != 0 && colorB.A != 0)
                        {
                            // then an intersection has been found
                            return true;
                        }
                    }
                }
            }

            // No intersection found
            return false;
        }

        public static Boolean IntersectPixels(GameObject gameObjectA, GameObject gameObjectB)
        {            
            // Find the bounds of the rectangle intersection
            Int32 top = Math.Max(gameObjectA.Transform.BoundingBox.Top, gameObjectB.Transform.BoundingBox.Top);
            Int32 bottom = Math.Min(gameObjectA.Transform.BoundingBox.Bottom, gameObjectB.Transform.BoundingBox.Bottom);
            Int32 left = Math.Max(gameObjectA.Transform.BoundingBox.Left, gameObjectB.Transform.BoundingBox.Left);
            Int32 right = Math.Min(gameObjectA.Transform.BoundingBox.Right, gameObjectB.Transform.BoundingBox.Right);

            // Check every point within the intersection bounds
            for (int y = top; y < bottom; y++)
            {
                for (int x = left; x < right; x++)
                {
                    // Get the color of both pixels at this point
                    Color colorA = gameObjectA.Renderer.ColourData[(x - gameObjectA.Transform.BoundingBox.Left) +
                                (y - gameObjectA.Transform.BoundingBox.Top) * gameObjectA.Transform.BoundingBox.Width];
                    Color colorB = gameObjectB.Renderer.ColourData[(x - gameObjectB.Transform.BoundingBox.Left) +
                                (y - gameObjectB.Transform.BoundingBox.Top) * gameObjectB.Transform.BoundingBox.Width];

                    // If both pixels are not completely transparent,
                    if (colorA.A != 0 && colorB.A != 0)
                    {
                        // then an intersection has been found
                        return true;
                    }
                }
            }

            // No intersection found
            return false;
        }
    }
}
